/******************************************************************************
 * Copyright (C) 2015  671643387@qq.com
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *****************************************************************************/

#include "Game/Game/DBCStores.h"
#include <list>
#include <string>
#include <algorithm>
#include "Shared/Common.h"
#include "Shared/Log/Log.h"
#include "Shared/Util/String.h"
#include "Shared/Util/ProgressBar.h"
#include "Game/Game/DBCFmt.h"
#pragma warning(disable:4996)
using namespace Shared;
using namespace Shared::Util;
using namespace Shared::DBC;

///////////////////////////////////////////////////////////////////////////////

typedef std::list<std::string> StoreProblemList;

static bool loadDBC_assert_print(std::uint32_t fsize, std::uint32_t rsize, const std::string& filename)
{
    sharedLog.outError("Size of '%s' setted by format string (%u) not equal size of C++ structure (%u).", filename.c_str(), fsize, rsize);

    // ASSERT must fail after function call
    return false;
}

template<class T>
inline void loadDBC(std::uint32_t& availableDbcLocales, BarGoLink& bar, StoreProblemList& errlist, DBCStorage<T>& storage, const std::string& dbc_path, const std::string& filename)
{
    // compatibility format and C++ structure sizes
    SHARED_ASSERT(DBCFileLoader::getFormatRecordSize(storage.getFormat()) == sizeof(T) 
        || loadDBC_assert_print(DBCFileLoader::getFormatRecordSize(storage.getFormat()), sizeof(T), filename));

    std::string dbc_filename = dbc_path + filename;
    if (storage.load(dbc_filename.c_str()))
    {
        bar.step();
        for (std::uint8_t i = 0; fullLocaleNameList[i].name; ++i)
        {
            if (!(availableDbcLocales & (1 << i)))
            { continue; }

            std::string dbc_filename_loc = dbc_path + fullLocaleNameList[i].name + "/" + filename;
            if (!storage.loadStringsFrom(dbc_filename_loc.c_str()))
            { availableDbcLocales &= ~(1 << i); }           // mark as not available for speedup next checks
        }
    }
    else
    {
        // sort problematic dbc to (1) non compatible and (2) nonexistent
        FILE* f = fopen(dbc_filename.c_str(), "rb");
        if (f)
        {
            char buf[100];
            Util::snprintf(buf, 100, " (exist, but have %u fields instead %s) Wrong client version DBC file?", storage.getFieldCount(), strlen(storage.getFormat()));
            errlist.push_back(dbc_filename + buf);
            fclose(f);
        }
        else
        { errlist.push_back(dbc_filename); }
    }
}

DBCStores *DBCStores::_instance = NULL;

DBCStores *DBCStores::instance(void)
{
    SHARED_ASSERT(_instance);
    return _instance;
}

DBCStores::DBCStores(void)
    : _chrClassesStore(ChrClassesEntryfmt)
    , _chrRacesStore(ChrRacesEntryfmt)
    , _creatureFamilyStore(CreatureFamilyfmt)
    , _factionStore(FactionEntryfmt)
    , _factionTeamMap()
    , _itemRandomPropertiesStore(ItemRandomPropertiesfmt)
    , _lockStore(LockEntryfmt)
    , _mapStore(MapEntryfmt)
    , _petFamilySpellsStore()
    , _skillLineAbilityStore(SkillLineAbilityfmt)
    , _spellStore(SpellEntryfmt)
    , _spellCategoryStore()
{
    SHARED_ASSERT(!_instance);
    _instance = this;
}

DBCStores::~DBCStores(void)
{

}

bool DBCStores::loadDBC(const std::string& dataPath)
{
    std::string dbcPath = dataPath + "dbc/";
    const std::uint32_t DBCFilesCount = 9;

    

    BarGoLink bar(DBCFilesCount);
    StoreProblemList bad_dbc_files;

    // bitmask for index of fullLocaleNameList
    std::uint32_t availableDbcLocales = 0xFFFFFFFF;

    ::loadDBC(availableDbcLocales, bar, bad_dbc_files, _chrClassesStore, dbcPath, "ChrClasses.dbc");
    ::loadDBC(availableDbcLocales, bar, bad_dbc_files, _chrRacesStore, dbcPath, "ChrRaces.dbc");
    ::loadDBC(availableDbcLocales, bar, bad_dbc_files, _creatureFamilyStore, dbcPath, "CreatureFamily.dbc");

    ::loadDBC(availableDbcLocales, bar, bad_dbc_files, _factionStore, dbcPath, "Faction.dbc");
    for (std::uint32_t i = 0; i < _factionStore.getNumRows(); ++i)
    {
        FactionEntry const* faction = _factionStore.lookupEntry(i);
        if (faction && faction->Team)
        {
            SimpleFactionsList& flist = _factionTeamMap[faction->Team];
            flist.push_back(i);
        }
    }

    ::loadDBC(availableDbcLocales, bar, bad_dbc_files, _itemRandomPropertiesStore, dbcPath, "ItemRandomProperties.dbc");

    ::loadDBC(availableDbcLocales, bar, bad_dbc_files, _lockStore, dbcPath, "Lock.dbc");
    ::loadDBC(availableDbcLocales, bar, bad_dbc_files, _mapStore, dbcPath, "Map.dbc");

    ::loadDBC(availableDbcLocales, bar, bad_dbc_files, _skillLineAbilityStore, dbcPath, "SkillLineAbility.dbc");
    ::loadDBC(availableDbcLocales, bar, bad_dbc_files, _spellStore, dbcPath, "Spell.dbc");
    for (std::uint32_t i = 1; i < _spellStore.getNumRows(); ++i)
    {
        SpellEntry const* spell = _spellStore.lookupEntry(i);
        if (spell && spell->Category)
        { _spellCategoryStore[spell->Category].insert(i); }

        // DBC not support uint64 fields but SpellEntry have SpellFamilyFlags mapped at 2 std::uint32_t fields
        // std::uint32_t field already converted to bigendian if need, but must be swapped for correct uint64 bigendian view
#if SHARED_ENDIAN == SHARED_BIGENDIAN
        std::swap(*((std::uint32_t*)(&spell->SpellFamilyFlags)), *(((std::uint32_t*)(&spell->SpellFamilyFlags)) + 1));
#endif
    }

    for (std::uint32_t j = 0; j < _skillLineAbilityStore.getNumRows(); ++j)
    {
        SkillLineAbilityEntry const* skillLine = _skillLineAbilityStore.lookupEntry(j);

        if (!skillLine)
        { continue; }

        SpellEntry const* spellInfo = _spellStore.lookupEntry(skillLine->SpellId);
        if (spellInfo && (spellInfo->Attributes & (SPELL_ATTR_UNK4 | SPELL_ATTR_PASSIVE | SPELL_ATTR_HIDE_SPELL | SPELL_ATTR_UNK8)) == (SPELL_ATTR_UNK4 | SPELL_ATTR_PASSIVE | SPELL_ATTR_HIDE_SPELL | SPELL_ATTR_UNK8))
        {
            for (unsigned int i = 1; i < _creatureFamilyStore.getNumRows(); ++i)
            {
                CreatureFamilyEntry const* cFamily = _creatureFamilyStore.lookupEntry(i);
                if (!cFamily)
                { continue; }

                if (skillLine->SkillId != cFamily->SkillLine[0] && skillLine->SkillId != cFamily->SkillLine[1])
                { continue; }

                _petFamilySpellsStore[i].insert(spellInfo->Id);
            }
        }
    }

    sharedLog.outString();
    if (bad_dbc_files.size() >= DBCFilesCount)
    {
        sharedLog.outError("\nIncorrect DataDir value in mangosd.conf or ALL required *.dbc files (%d) not found by path: %sdbc", DBCFilesCount, dataPath.c_str());
        return false;
    }
    else if (!bad_dbc_files.empty())
    {
        std::string str;
        for (std::list<std::string>::iterator i = bad_dbc_files.begin(); i != bad_dbc_files.end(); ++i)
        { 
            sharedLog.outError("%s", (*i).c_str());
        }

        return false;
    }

    sharedLog.outString(">> Initialized %d data stores", DBCFilesCount);

    return true;
}